Java 容器源码分析之 Set
Set 表示由无重复对象组成的集合,也是集合框架中重要的一种集合类型,直接扩展自 Collection 接口。在一个 Set 中,不能有两个引用指向同一个对象,或两个指向 null 的引用。如果对象 a 和 b 的引用满足条件 a.equals(b),那么这两个对象也不能同时出现在集合中。
通常 Set 是不要求元素有序的,但也有一些有序的实现,如 SortedMap 接口、LinkedHashSet 接口等。
概述
Set 的具体实现通常都是基于 Map 的。因为 Map 中键是唯一的,因而在基于 Map 实现 Set 时,只需要关心 Map 中的键,和键关联的值不需要有意义,使用一个任意的对象“占位”即可。我们在前面分析 Map 中的迭代器时,KeySet() 方法得到的就是一个 Set。
前面我们分析过 Map 接口的几个具体实现,通用的实现 HahsMap
,插入或访问序的 LinkedHashMap
, 按照键升序的 TreeMap
。同样,在 Set 的具体实现中,也有 HashSet
、 LinkedHashSet
和 TreeSet
等,分别和 Map 一一对应,它们的特性对应着相应的 Map 实现的特性。下面基于 HashSet 的实现做一个简略的介绍。
HashSet 的实现
1
|
|
从成员变量和构造方法可以清楚地看到,内部使用了一个 HahsMap,同时定义了一个无意义的空的静态 Object 对象(占用8byte) PRESENT。既然 map 中和键关联的值没有意义,为什么不干脆使用 null 呢?我们看一下 add() 方法:
1
|
public boolean add(E e) {
|
Map 的 put() 方法在添加一个新的键时会返回 null,在更新一个已经存在的键关联的值时会返回旧值。因而 Set 中的 add() 方法可以据此判断新加入的元素是否改变了集合,如果改变了就返回 true。因而 PRESENT 不可以使用 null 。
其它的方法这里简单地列一下,都是基于 map 实现的:
1
|
public boolean contains(Object o) {
|
小结
Set 的内部通常是基于 Map 来实现的,Map 中的 Key 构成了 Set,而 Value 全部使用一个无意义的 Object 。 Set 的特征与其内部的 Set 的特征是一致的。基于 HashMap 的 HashSet 是无序时的最佳通用实现,基于 LinkedHashMap 的 LinkedHashSet 保留插入或访问的顺序,基于 TreeMap 的 TreeSet 可以按照元素升序排列,要求元素实现 Comaprable 接口或自定义比较器。
HashSet , LinkedHashSet, TreeSet 都不是线程安全的,在多线程环境下使用时要注意同步问题。
CopyOnWriteArraySet 是一个线程安全的实现,但是并不是基于 Map 实现的,而是通过 CopyOnWriteArrayList 实现的。使用 addIfAbsent() 方法进行去重,性能比较一般。